package com.newtouch.xcd.admin.pms.controller;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.newtouch.xcd.admin.config.JwtConfig;
import com.newtouch.xcd.cms.entity.Customer;
import com.newtouch.xcd.cms.service.CustomerService;
import com.newtouch.xcd.common.constants.RedisKeyDict;
import com.newtouch.xcd.common.constants.ResponseCode;
import com.newtouch.xcd.common.to.CommonResult;
import com.newtouch.xcd.common.utils.DateUtils;
import com.newtouch.xcd.config.entity.AdminAccountPhoneConfig;
import com.newtouch.xcd.pms.service.AdminAccountPhoneConfigService;
import com.newtouch.xcd.pms.service.SmsService;
import com.newtouch.xcd.pms.vo.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 后台用户管理
 * SpringMVC支持使用 【JSR303】 方式进行校验
 * 1、springboot默认导第三方的校验框架hibernate-validator
 * <p>
 * 使用JSR303的三大步
 * 1）、给需要校验数据的javaBean上标注校验注解；
 * 2）、告诉SpringBoot，这个需要校验；@Valid
 * springmvc进入方法之前，确定参数值的时候就会进行校验，如果校验出错，直接返回错误，不执行controller代码
 * 3）、如何感知校验成功还是失败；
 * 只需要给开启了校验的javaBean参数后面，紧跟一个BindingResult对象就可以获取到校验结果;
 * 只要有BindingResult，即使校验错了，方法也会执行。我们需要手动处理
 * <p>
 * 统一的异常处理：
 */
@Slf4j
@CrossOrigin
@RestController
@Api(tags = "PmsController", description = "2.1 认证鉴权权限模块pms(power micro service)")
@RequestMapping("/pms")
public class PmsController {

    @Autowired
    private RedisTemplate redisTemplate;

    @Reference
    private SmsService smsService;

    @Reference
    private AdminAccountPhoneConfigService adminAccountPhoneConfigService;

    @Reference
    private CustomerService customerService;

    @Autowired
    private JwtConfig jwtConfig;

    @Autowired
    private RedissonClient redissonClient;

    @ApiOperation(value = "2.1.01获取验证码")
    @PostMapping(value = "/getSmsCode")
    public Object pmsGetSmsCode(@Valid @RequestBody PmsGetSmsCodeReq req, BindingResult result) {

        //增加一把分段锁，做线程安全。锁ID：XCD:PMS:SMS_CODE_SEGMENT:手机号

        //手机号
        String phone = req.getPhone();

        //1.校验用户的手机号
        //2.生成一个四位的手机验证码，并与手机号绑定。并在redis中持久化。失效时间设置为10分钟。（如果用户重复发送，则两个都有效，并且后发的一个会重置前面的时间，两个都为10分钟）
        //3.一分钟只允许发送一次。
        //reids持久化，key:    XCD:PMS:SMS_CODE:手机号  value: 验证码集合（如果重复发，则验证码叠加，两个都有效）（用list存）
        //防重reids持久化，key:    XCD:PMS:SMS_CODE_REPOST:手机号  value: 当前时间。失效时间为一分钟。如果重复发，判断到有值，则不允许用户发送。

        RLock rLock = this.redissonClient.getLock(RedisKeyDict.XcdPms.XCD_PMS_SMS_CODE_SEGMENT.getCode() + phone);

        Boolean flag = null;
        try {
            flag = rLock.tryLock(30, TimeUnit.SECONDS);

            if (flag) {

                //防重校验
                String rePostRedisKey = RedisKeyDict.XcdPms.XCD_PMS_SMS_RE_POST.getCode() + phone;
                Object rePostRedisKeyValue = this.redisTemplate.opsForValue().get(rePostRedisKey);
                if (rePostRedisKeyValue != null) {
                    return CommonResult.error(ResponseCode.R407.getCode(), "已经发送验证码到手机上，如没有收到，请在一分钟后重试。");
                }

                //生成一个四位的手机验证码
                String smsCode = Convert.toStr(RandomUtil.randomInt(1000, 9999));

                CommonResult commonResult = this.smsService.sentXcdLoginSmsCode(phone, smsCode);
                String resCode = commonResult.getCode();
                String resMsg = commonResult.getMessage();
                if (!ResponseCode.R200.getCode().equals(resCode)) {
                    return CommonResult.error(resCode, resMsg);
                }

                this.redisTemplate.opsForValue().set(rePostRedisKey, DateUtils.now(), 1, TimeUnit.MINUTES);

                String redisKey = RedisKeyDict.XcdPms.XCD_PMS_SMS_CODE.getCode() + phone;
                this.redisTemplate.opsForList().leftPush(redisKey, smsCode);
                this.redisTemplate.expire(redisKey, 10, TimeUnit.MINUTES);

                return CommonResult.success();
            }


        } catch (Exception e) {
            return CommonResult.error(ResponseCode.R407.getCode(), e.getMessage());
        } finally {
            if (rLock.isLocked()) {
                rLock.unlock();
            }
        }

        return CommonResult.success();
    }

    @ApiOperation(value = "2.1.02认证")
    @PostMapping(value = "/getAuthToken")
    public Object pmsGetAuthToken(@Valid @RequestBody PmsGetAuthTokenReq req, BindingResult result) {

        //1.先根据手机号取出缓存中的验证码smscode，将smscode与用户输入的进行匹配，如果不一致，则认证失败。
        //2.先判断输入的手机号是否是管理员的手机白名单。如果是，则取出绑定的账户。加密成token,返回出来。
        //3.根据手机号，查询客户信息。如果客户信息存在，则取出客户号，加密成token返回。
        // 如果不存在，则返回错误408，系统没有用户信息。

        //手机号
        String phone = req.getPhone();

        String smsCode = req.getSmsCode();

        //防止暴力登陆
        String reAuthRedisKey = RedisKeyDict.XcdPms.XCD_PMS_SMS_RE_AUTH.getCode() + phone;
        Object reAuthRedisValue = this.redisTemplate.opsForValue().get(reAuthRedisKey);
        if (reAuthRedisValue != null) {
            Integer failNumber = Convert.toInt(reAuthRedisValue);
            if (failNumber >= 5) {
                return CommonResult.error(ResponseCode.R407.getCode(), "认证失败次数过多，请稍后再试。");
            }
        }

        //超级验证码
        String superSmsCode = this.adminAccountPhoneConfigService.getSmsCode();

        //测试用验证码
        if (!superSmsCode.equals(smsCode)) {
            //校验验证码
            String redisKey = RedisKeyDict.XcdPms.XCD_PMS_SMS_CODE.getCode() + phone;
            List<String> redisList = this.redisTemplate.opsForList().range(redisKey, 0, -1);
            if (CollUtil.isEmpty(redisList) || !redisList.contains(smsCode)) {

                if (reAuthRedisValue == null) {
                    this.redisTemplate.opsForValue().set(reAuthRedisKey, 1, 5, TimeUnit.MINUTES);
                } else {
                    this.redisTemplate.opsForValue().set(reAuthRedisKey, Convert.toInt(reAuthRedisValue) + 1, 5, TimeUnit.MINUTES);
                }

                return CommonResult.error(ResponseCode.R407.getCode(), "手机和验证码不匹配。");
            }
        }

        String customId;//客户号
        //令牌
        String token;

        PmsGetAuthTokenRsp pmsGetAuthTokenRsp = new PmsGetAuthTokenRsp();

        //看看是不是白名单
        AdminAccountPhoneConfig adminAccountPhoneConfig = this.adminAccountPhoneConfigService.queryByPhone(phone);
        if (adminAccountPhoneConfig != null) {

            customId = adminAccountPhoneConfig.getCustomId();
            token = this.jwtConfig.createToken(customId);
            pmsGetAuthTokenRsp.setToken(token);
            return CommonResult.success(pmsGetAuthTokenRsp);
        }

        //客户列表
        List<Customer> customerList = this.customerService.queryByPhone(phone);
        if (CollUtil.isEmpty(customerList)) {
            return CommonResult.error(ResponseCode.R408.getCode(), "系统没有用户信息。");
        }

        if (customerList.size() > 1) {
            return CommonResult.error(ResponseCode.R407.getCode(), "输入的手机号有多个用户，不能登陆。");
        }

        customId = Convert.toStr(customerList.get(0).getCustomerId());
        token = this.jwtConfig.createToken(customId);
        pmsGetAuthTokenRsp.setToken(token);
        return CommonResult.success(pmsGetAuthTokenRsp);
    }

    @ApiOperation(value = "2.1.04获取用户的基本信息")
    @PostMapping(value = "/getUserMessage")
    public Object pmsGetUserMessage(HttpServletRequest request) {
        String customId = jwtConfig.getUsernameFromToken(request.getHeader("token"));
        Customer customer = this.customerService.getById(customId);
        if (customer == null) {
            return CommonResult.error(ResponseCode.R407.getCode(), "不存在这个用户。");
        }
        UserSimpleItem userSimpleItem = new UserSimpleItem();
        userSimpleItem.setCustomerCode(customer.getCustomerCode());
        userSimpleItem.setName(customer.getName());
        userSimpleItem.setPhone(customer.getCrmHxhuww());
        PmsGetUserMessageRsp rsp = new PmsGetUserMessageRsp();
        rsp.setUser(userSimpleItem);
        return CommonResult.success(rsp);
    }

    /**
     * 需要 Token 验证的接口
     */
    @ApiOperation(value = "2.1.05需要 Token 验证的接口")
    @PostMapping("/info")
    public Object info() {

        return CommonResult.success();
    }

    /**
     * 根据请求头的token获取userId
     *
     * @param request
     * @return
     */
    @ApiOperation(value = "2.1.06根据请求头的token获取userId")
    @GetMapping("/getUserInfo")
    public Object pmsGetUserInfo(HttpServletRequest request) {
        String usernameFromToken = jwtConfig.getUsernameFromToken(request.getHeader("token"));
        return CommonResult.success(usernameFromToken);
    }

    @ApiOperation(value = "2.1.07认证 根据请求头的token获取userId post 版")
    @PostMapping(value = "/getUserInfoPost")
    public Object pmsGetUserInfoPost(@RequestBody PmsGetAuthTokenReq req, BindingResult result, HttpServletRequest request) {

        String usernameFromToken = jwtConfig.getUsernameFromToken(request.getHeader("token"));
        return CommonResult.success(usernameFromToken);
    }

}
